前面寫了這麼多篇文章,都沒提到關於樣式的設定。
今天我們終於要進入樣式了,Web Component 的樣式其實是我在學習過程中時常卡關的部分。
因為樣式隔離的關係,外部 CSS 沒辦法直接改變 Shadow DOM 裡面元素的 style,這也可以說是 Web Component 一直的一個缺點吧。
在 套用樣式
的過程中,可能會體會到 Shadow DOM 的雙面刃。
Shadow DOM 在某些情境下是好事,因為保證元件樣式不會被外部影響,但在另一面卻可能造成麻煩。
當你的網站有主題顏色(theme colors)或是想統一字型、按鈕樣式時,是無法直接在外部修改的,所以我們會需要用到其他方法,讓外部套用者可以修改在 Shadow DOM 內元件的樣式。
我們先建立一個簡單的 Shadow DOM 元件,試試看在外部覆蓋元件的樣式:
card.js
class UNCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.card {
width: 100px;
height: 20px;
padding: 12px;
border: 2px solid #888888;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
}
</style>
<div class='card'>請幫我加入樣式</div>
`;
}
}
customElements.define('un-card', UNCard);
index.html
<style>
un-card .card {
background-color: #222222;
}
</style>
<body>
<un-card></un-card>
</body>
當外部針對卡片元件改變背景樣式時,會發現元件的背景色並未改變。
在前面的文章,其實已經可以透過學到的 attribute
或是 property
來改變元件的樣式。
像是在前面的示範中,我們透過監聽 color
的 attribute 來改變 cat-spinner
中貓咪的顏色。
而今天我們會使用 css 來控制元件樣式,以下有三種方式,可以讓外部改變 Shadow DOM 中的元件樣式。
透過在 Web Component 的樣式中定義 :host
來給予基本元件的樣式。
並且可以再針對特定樣式給予特定 host,像是 :host([primary])
,這樣子,外部就可以在標籤上面加入我們已經定義好的一些樣式。
這種做法比較像是元件已經寫好了幾種樣式讓使用者切換,可能是主題色像是 primary
、secondary
...。
card.js
class UNCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
width: 100px;
height: 20px;
padding: 12px;
border: 2px solid #888888;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
}
:host([primary]) {
border-color: #ad43d3;
}
:host([rounded-xl]) {
border-radius: 20px;
}
</style>
<div class='card'>請幫我加入樣式</div>
`;
}
}
customElements.define('un-card', UNCard);
index.html
<body style="display: flex; gap: 8px">
<un-card></un-card>
<un-card primary></un-card>
<un-card primary rounded-xl></un-card>
</body>
CSS 變數可以突破 Shadow DOM 的隔離限制
,外部 CSS 不能直接進 Shadow DOM 修改樣式,但是 CSS 變數卻可以穿透 Shadow DOM ,讓外部來控制內部樣式。
外部可以更靈活的替換 Shadow DOM 元件內的樣式,若有主題色的話也可以更快速的更換。
card.js
class UNCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
width: 100px;
height: 20px;
padding: 12px;
border: 2px solid var(--card-border-color, #333333);
border-radius: var(--card-radius, 8px);
color: var(--card-text-color, #888888);
font-size: 14px;
font-weight: 600;
}
</style>
<div class='card'>請幫我加入樣式</div>
`;
}
}
customElements.define('un-card', UNCard);
index.html
<style>
un-card {
--card-border-color: #a3ddfa;
--card-radius: 8px;
--card-text-color: #dffabc;
}
</style>
<body style="display: flex; gap: 8px">
<un-card></un-card>
</body>
part
跟前面所說到的 host 其實蠻相似的,但是 part
讓使用者可以彈性調整樣式的幅度比 host 還要更大。
有標記 part 的元素
,定義外部可以修改哪些元素的樣式,哪些不行,以達到有限度的開放。card.js
class UNCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.card {
padding: 8px;
border: 1px solid #33e4fd;
}
</style>
<div part="card" class="card">請幫我加入樣式</div>
`;
}
}
customElements.define('un-card', UNCard);
index.html
<style>
un-card::part(card) {
border: 1px dashed #aad332;
padding: 12px;
border-radius: 4px;
}
</style>
<body style="display: flex; gap: 8px">
<un-card></un-card>
</body>
以上三種方法,就是可以改變 Web Component 樣式的方法。
而要如何使用,就看設計元件的你,想要讓外部使用者有多大的彈性囉!